﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Linq.Expressions;

namespace Fluq.Tests
{
	sealed class MockProxy : RealProxy, IRemotingTypeInfo
	{
		private object _instance;
		private MethodInfo _disposeMethod;
		private Mock _mock;

		public MockProxy(object instance, Type abstractType, Mock mock)
			: base(abstractType)
		{
			_instance = instance;
			_mock = mock;
			_disposeMethod = Method<IDisposable>(o => (Action)o.Dispose);
		}

		internal static MethodInfo Method<TType>(Expression<Func<TType, Delegate>> expression)
		{
			return MethodExpression(expression);
		}

		public static Mock GetMock(object instance)
		{
			MockProxy proxy = (MockProxy)RemotingServices.GetRealProxy(instance);

			return proxy._mock;
		}

		internal static MethodInfo MethodExpression(LambdaExpression expression)
		{
			UnaryExpression unary = expression.Body as UnaryExpression;

			if (unary != null)
			{
				MethodCallExpression call = unary.Operand as MethodCallExpression;

				if (call != null)
				{
					foreach (Expression o in call.Arguments)
					{
						ConstantExpression ce = o as ConstantExpression;

						if (ce != null)
						{
							MethodInfo method = ce.Value as MethodInfo;

							if (method != null)
							{
								return method;
							}
						}
					}
				}
			}

			throw new ArgumentOutOfRangeException("expression", "The lambda expression is not a valid unary method constant.");
		}

		private static bool SignaturesEqual(MethodInfo method, MethodInfo methodToCompare)
		{
			if (method.ReturnType != methodToCompare.ReturnType)
			{
				return false;
			}

			ParameterInfo[] methodParameters = method.GetParameters();
			ParameterInfo[] parametersToCompare = methodToCompare.GetParameters();

			if (methodParameters.Length != parametersToCompare.Length)
			{
				return false;
			}

			for (int i = 0; i < methodParameters.Length; i++)
			{
				ParameterInfo methodParameter = methodParameters[i];
				ParameterInfo parameterToCompare = parametersToCompare[i];

				if (methodParameter.ParameterType != parameterToCompare.ParameterType)
				{
					return false;
				}

				if (methodParameter.Attributes != parameterToCompare.Attributes)
				{
					return false;
				}
			}

			return true;
		}

		private static bool MethodsEqual(MethodInfo method, MethodInfo methodToCompare)
		{
			if (String.Equals(MethodName(method), MethodName(methodToCompare)))
			{
				return true;
			}

			return SignaturesEqual(method, methodToCompare);
		}

		private static string MethodName(MethodInfo method)
		{
			Type declaringType = method.DeclaringType;

			if (!declaringType.IsInterface)
			{
				foreach (Type interfaceType in declaringType.GetInterfaces())
				{
					InterfaceMapping mapping = declaringType.GetInterfaceMap(interfaceType);

					int index = Array.IndexOf(mapping.TargetMethods, method);

					if (index != -1)
					{
						MethodInfo interfaceMethod = mapping.InterfaceMethods[index];

						return interfaceMethod.Name;
					}
				}
			}

			return method.Name;
		}

		public override IMessage Invoke(IMessage msg)
		{
			IMethodCallMessage call = msg as IMethodCallMessage;

			if (call != null)
			{
				MethodInfo methodInfo = (MethodInfo)call.MethodBase;

				if (MethodsEqual(_disposeMethod, methodInfo))
				{
					_mock.OnDispose(this);

					return new ReturnMessage(null, null, 0, call.LogicalCallContext, call);
				}
				else
				{
					object returnValue = methodInfo.Invoke(_instance, call.Args);

					return new ReturnMessage(returnValue, call.Args, call.ArgCount, call.LogicalCallContext, call);
				}
			}

			return null;
		}

		bool IRemotingTypeInfo.CanCastTo(Type fromType, object o)
		{
			Console.WriteLine("t = " + _instance.GetType());

			if (fromType == typeof(IDisposable))
			{
				return true;
			}
			else if (fromType == _instance.GetType())
			{
				return true;
			}

			return false;
		}

		string IRemotingTypeInfo.TypeName
		{
			get
			{
				return _instance.GetType().FullName;
			}
			set
			{
				throw new NotImplementedException();
			}
		}
	}
}
